tg-me.com/database_info/1478
Last Update:
Антипаттерн: N+1 запросов и как его избежать
Что такое N+1?
При выборке связанных данных ORM (или вручную) сначала делается 1 запрос за основными записями, а потом N дополнительных — по одной для каждой записи, чтобы получить связанные объекты. Например, получить 10 пользователей и для каждого — список их заказов ⇒ 1 запрос к users
+ 10 запросов к orders
. 🚩
# SQLAlchemy-пример “N+1”:
users = session.query(User).all() # 1 запрос
for u in users:
print(u.orders) # для каждого пользователя — отдельный запрос
Почему плохо?
🔹 Высокая нагрузка на базу: запросы “в тоненькую” вместо одного “тяжелого”.
🔹 Задержки сети: множество раунд-трипов увеличивает время ответа.
🔹 Масштабируемость страдает: при росте N время растёт линейно.
Как победить N+1
1. Eager loading (предварительная загрузка)
Загрузка связей сразу вместе с основными объектами.
# SQLAlchemy, joinedload — делает JOIN и подтягивает данные сразу
from sqlalchemy.orm import joinedload
users = session.query(User).options(joinedload(User.orders)).all()
for u in users:
print(u.orders) # не генерирует дополнительных запросов
✅ Сокращает число запросов до 1.
2. Batch loading (групповые запросы)
Если JOIN приводит к дублированию полей, можно сделать два запроса:
-- 1: получить user_id
SELECT id FROM users WHERE active = true;
-- 2: получить все заказы для этих пользователей
SELECT * FROM orders WHERE user_id IN (...список id...);
✅ Баланс между сложностью и производительностью.
3. DataLoader / кеширование
В GraphQL и приложениях на Node.js часто используют DataLoader:
🔹 Собирает все ключи за тиковый цикл
🔹 Делает один общий запрос
🔹 Раздаёт результаты обратно
4. Правильное проектирование API
— Предусматривайте, какие связи нужны на фронтенде, и загружайте их сразу.
— Разделяйте endpoints: если нужны только пользователи без заказов — делайте лёгкий запрос.
Best practices & подводные камни
🔹 EXPLAIN ANALYZE для проверки плана: убедитесь, что JOIN-ы и IN (…) не приводят к полному сканированию таблиц.
🔹 Пагинация: всегда ограничивайте выборку через
LIMIT/OFFSET
или курсоры.🔹 Будьте осторожны с joinedload на “много ко многим” — может раздувать размер результата.
Сохрани этот пост, чтобы не забыть, и поделись с коллегами!
А у тебя были случаи, когда N+1 съедал всю производительность? Как борешься?
#db
👉 @database_info
BY Базы данных (Data Base)

Share with your friend now:
tg-me.com/database_info/1478